#include "pluginsystem.h"
#include <QCoreApplication>
#include <QStandardPaths>
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QDebug>
#include <QFileInfo>
#include <QCryptographicHash>
#include <QSettings>
#include <QTimer>

PluginSystem::PluginSystem(QObject *parent)
    : QObject(parent)
    , m_sandboxEnabled(false)
{
    // Set default plugin directories
    m_pluginDirectories << QCoreApplication::applicationDirPath() + "/plugins"
                       << QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/plugins";
    
    loadPluginConfigs();
    setupIPC();
}

PluginSystem::~PluginSystem()
{
    // Unload all plugins
    for (auto it = m_loadedPlugins.begin(); it != m_loadedPlugins.end(); ++it) {
        unloadPlugin(it.key());
    }
    
    savePluginConfigs();
}

bool PluginSystem::loadPlugin(const QString &pluginPath)
{
    QFileInfo fileInfo(pluginPath);
    if (!fileInfo.exists()) {
        qWarning() << "Plugin file does not exist:" << pluginPath;
        return false;
    }
    
    // Parse plugin metadata
    PluginInfo pluginInfo = parsePluginMetadata(pluginPath);
    if (pluginInfo.name.isEmpty()) {
        qWarning() << "Invalid plugin metadata:" << pluginPath;
        return false;
    }
    
    // Check if plugin is already loaded
    if (m_loadedPlugins.contains(pluginInfo.name)) {
        qWarning() << "Plugin already loaded:" << pluginInfo.name;
        return false;
    }
    
    // Validate plugin signature if security is enabled
    if (!validatePluginSignature(pluginPath)) {
        qWarning() << "Plugin signature validation failed:" << pluginPath;
        return false;
    }
    
    // Check dependencies
    if (!checkDependencies(pluginInfo)) {
        qWarning() << "Plugin dependencies not met:" << pluginInfo.name;
        return false;
    }
    
    // Load the plugin
    QPluginLoader *loader = new QPluginLoader(pluginPath, this);
    if (!loader->load()) {
        qWarning() << "Failed to load plugin:" << loader->errorString();
        delete loader;
        return false;
    }
    
    // Get plugin instance
    QObject *pluginObject = loader->instance();
    IAuditPlugin *plugin = qobject_cast<IAuditPlugin*>(pluginObject);
    
    if (!plugin) {
        qWarning() << "Plugin does not implement IAuditPlugin interface:" << pluginPath;
        loader->unload();
        delete loader;
        return false;
    }
    
    // Initialize plugin
    if (!plugin->initialize(pluginInfo.config)) {
        qWarning() << "Plugin initialization failed:" << pluginInfo.name;
        loader->unload();
        delete loader;
        return false;
    }
    
    // Setup sandbox if enabled
    if (m_sandboxEnabled) {
        setupPluginSandbox(pluginInfo.name);
    }
    
    // Store plugin information
    pluginInfo.loaded = true;
    pluginInfo.enabled = true;
    m_availablePlugins[pluginInfo.name] = pluginInfo;
    m_loadedPlugins[pluginInfo.name] = loader;
    m_pluginInstances[pluginInfo.name] = plugin;
    m_pluginObjects[pluginInfo.name] = pluginObject; // Store QObject pointer
    
    // Connect destruction signal
    connect(pluginObject, &QObject::destroyed, this, &PluginSystem::onPluginDestroyed);
    
    qInfo() << "Plugin loaded successfully:" << pluginInfo.name;
    emit pluginLoaded(pluginInfo.name);
    
    return true;
}

bool PluginSystem::unloadPlugin(const QString &pluginName)
{
    if (!m_loadedPlugins.contains(pluginName)) {
        return false;
    }
    
    // Get plugin instance and shutdown
    if (m_pluginInstances.contains(pluginName)) {
        IAuditPlugin *plugin = m_pluginInstances[pluginName];
        plugin->shutdown();
        m_pluginInstances.remove(pluginName);
        m_pluginObjects.remove(pluginName); // Remove QObject pointer too
    }
    
    // Unload plugin
    QPluginLoader *loader = m_loadedPlugins[pluginName];
    if (loader) {
        loader->unload();
        delete loader;
        m_loadedPlugins.remove(pluginName);
    }
    
    // Update plugin info
    if (m_availablePlugins.contains(pluginName)) {
        m_availablePlugins[pluginName].loaded = false;
        m_availablePlugins[pluginName].enabled = false;
    }
    
    // Cleanup
    cleanupPlugin(pluginName);
    
    qInfo() << "Plugin unloaded:" << pluginName;
    emit pluginUnloaded(pluginName);
    
    return true;
}

bool PluginSystem::enablePlugin(const QString &pluginName)
{
    if (!m_availablePlugins.contains(pluginName)) {
        return false;
    }
    
    PluginInfo &info = m_availablePlugins[pluginName];
    if (!info.loaded) {
        return loadPlugin(info.filePath);
    }
    
    info.enabled = true;
    return true;
}

bool PluginSystem::disablePlugin(const QString &pluginName)
{
    if (!m_availablePlugins.contains(pluginName)) {
        return false;
    }
    
    m_availablePlugins[pluginName].enabled = false;
    return true;
}

QStringList PluginSystem::scanForPlugins(const QString &directory)
{
    QStringList pluginFiles;
    QDir pluginDir(directory);
    
    if (!pluginDir.exists()) {
        return pluginFiles;
    }
    
    // Look for .dll, .so, .dylib files
    QStringList filters;
    filters << "*.dll" << "*.so" << "*.dylib";
    
    QFileInfoList files = pluginDir.entryInfoList(filters, QDir::Files);
    for (const QFileInfo &fileInfo : files) {
        pluginFiles << fileInfo.absoluteFilePath();
    }
    
    return pluginFiles;
}

QList<PluginInfo> PluginSystem::getAvailablePlugins() const
{
    return m_availablePlugins.values();
}

QList<PluginInfo> PluginSystem::getLoadedPlugins() const
{
    QList<PluginInfo> loadedPlugins;
    for (const PluginInfo &info : m_availablePlugins.values()) {
        if (info.loaded) {
            loadedPlugins << info;
        }
    }
    return loadedPlugins;
}

IAuditPlugin* PluginSystem::getPlugin(const QString &pluginName) const
{
    return m_pluginInstances.value(pluginName, nullptr);
}

bool PluginSystem::callPluginMethod(const QString &pluginName, const QString &method, 
                                   const QJsonObject &params, QJsonObject &result)
{
    IAuditPlugin *plugin = getPlugin(pluginName);
    if (!plugin) {
        return false;
    }
    
    // Handle different method calls
    if (method == "logEvent") {
        QString userId = params["userId"].toString();
        QString action = params["action"].toString();
        QString details = params["details"].toString();
        QJsonObject metadata = params["metadata"].toObject();
        
        bool success = plugin->logEvent(userId, action, details, metadata);
        result["success"] = success;
        return success;
    }
    else if (method == "queryEvents") {
        QJsonObject filter = params["filter"].toObject();
        QJsonArray events = plugin->queryEvents(filter);
        result["events"] = events;
        return true;
    }
    else if (method == "exportData") {
        QJsonObject exportConfig = params["exportConfig"].toObject();
        bool success = plugin->exportData(exportConfig);
        result["success"] = success;
        return success;
    }
    else if (method == "authenticateUser") {
        QString username = params["username"].toString();
        QString password = params["password"].toString();
        bool success = plugin->authenticateUser(username, password);
        result["authenticated"] = success;
        return success;
    }
    else if (method == "hasPermission") {
        QString userId = params["userId"].toString();
        QString permission = params["permission"].toString();
        bool hasPermission = plugin->hasPermission(userId, permission);
        result["hasPermission"] = hasPermission;
        return true;
    }
    
    return false;
}

bool PluginSystem::setPluginConfig(const QString &pluginName, const QJsonObject &config)
{
    if (!m_availablePlugins.contains(pluginName)) {
        return false;
    }
    
    m_availablePlugins[pluginName].config = config;
    
    // If plugin is loaded, reinitialize with new config
    if (m_pluginInstances.contains(pluginName)) {
        IAuditPlugin *plugin = m_pluginInstances[pluginName];
        plugin->shutdown();
        return plugin->initialize(config);
    }
    
    return true;
}

QJsonObject PluginSystem::getPluginConfig(const QString &pluginName) const
{
    if (m_availablePlugins.contains(pluginName)) {
        return m_availablePlugins[pluginName].config;
    }
    return QJsonObject();
}

bool PluginSystem::registerAsExternalPlugin(const QString &hostAppId)
{
    QJsonObject hostInfo;
    hostInfo["id"] = hostAppId;
    hostInfo["registered_at"] = QDateTime::currentDateTime().toString(Qt::ISODate);
    hostInfo["status"] = "active";
    
    m_hostApplications[hostAppId] = hostInfo;
    
    qInfo() << "Registered as external plugin for host application:" << hostAppId;
    return true;
}

bool PluginSystem::unregisterFromExternalApp(const QString &hostAppId)
{
    if (m_hostApplications.contains(hostAppId)) {
        m_hostApplications.remove(hostAppId);
        qInfo() << "Unregistered from host application:" << hostAppId;
        return true;
    }
    return false;
}

void PluginSystem::sendEventToHost(const QString &hostAppId, const QString &event, const QJsonObject &data)
{
    if (!m_hostApplications.contains(hostAppId)) {
        qWarning() << "Host application not registered:" << hostAppId;
        return;
    }
    
    QJsonObject message;
    message["type"] = "event";
    message["event"] = event;
    message["data"] = data;
    message["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
    message["source"] = "AuditTrailSystem";
    
    // In a real implementation, this would use IPC mechanisms like named pipes, sockets, etc.
    emit hostEventReceived(hostAppId, event, data);
}

void PluginSystem::registerEventHandler(const QString &event, std::function<void(const QJsonObject&)> handler)
{
    m_eventHandlers[event] = handler;
}

bool PluginSystem::validatePluginSignature(const QString &pluginPath)
{
    // Simplified signature validation
    // In a real implementation, this would verify digital signatures
    
    if (m_trustedPluginPaths.contains(pluginPath)) {
        return true;
    }
    
    // For now, just check if the plugin is in a trusted directory
    for (const QString &trustedDir : m_pluginDirectories) {
        if (pluginPath.startsWith(trustedDir)) {
            return true;
        }
    }
    
    return false;
}

bool PluginSystem::checkPluginPermissions(const QString &pluginName, const QStringList &requiredPermissions)
{
    if (!m_availablePlugins.contains(pluginName)) {
        return false;
    }
    
    const PluginInfo &info = m_availablePlugins[pluginName];
    
    // Check if plugin has all required permissions
    for (const QString &permission : requiredPermissions) {
        if (!info.permissions.contains(permission)) {
            return false;
        }
    }
    
    return true;
}

void PluginSystem::setSandboxMode(bool enabled)
{
    m_sandboxEnabled = enabled;
}

bool PluginSystem::isSandboxed() const
{
    return m_sandboxEnabled;
}

bool PluginSystem::sendMessageToPlugin(const QString &pluginName, const QString &message, const QJsonObject &data)
{
    // This would be implemented using plugin-specific communication mechanisms
    emit messageReceived(pluginName, message, data);
    return true;
}

void PluginSystem::broadcastMessage(const QString &message, const QJsonObject &data)
{
    for (const QString &pluginName : m_pluginInstances.keys()) {
        sendMessageToPlugin(pluginName, message, data);
    }
}

void PluginSystem::refreshPlugins()
{
    // Scan all plugin directories
    for (const QString &directory : m_pluginDirectories) {
        QStringList pluginFiles = scanForPlugins(directory);
        
        for (const QString &pluginFile : pluginFiles) {
            PluginInfo info = parsePluginMetadata(pluginFile);
            if (!info.name.isEmpty() && !m_availablePlugins.contains(info.name)) {
                m_availablePlugins[info.name] = info;
            }
        }
    }
}

void PluginSystem::reloadPlugin(const QString &pluginName)
{
    if (m_availablePlugins.contains(pluginName)) {
        QString pluginPath = m_availablePlugins[pluginName].filePath;
        unloadPlugin(pluginName);
        loadPlugin(pluginPath);
    }
}

void PluginSystem::onPluginDestroyed(QObject *plugin)
{
    // Find and remove the destroyed plugin using the QObject map
    for (auto it = m_pluginObjects.begin(); it != m_pluginObjects.end(); ++it) {
        if (it.value() == plugin) {
            const QString pluginName = it.key();
            
            // Remove from all maps
            m_pluginObjects.erase(it);
            m_pluginInstances.remove(pluginName);
            
            if (m_availablePlugins.contains(pluginName)) {
                m_availablePlugins[pluginName].loaded = false;
            }
            
            emit pluginError(pluginName, "Plugin was unexpectedly destroyed");
            return; // Exit early since we found the plugin
        }
    }
}

PluginInfo PluginSystem::parsePluginMetadata(const QString &pluginPath)
{
    PluginInfo info;
    info.filePath = pluginPath;
    
    // Try to load plugin temporarily to get metadata
    QPluginLoader loader(pluginPath);
    QJsonObject metaData = loader.metaData().value("MetaData").toObject();
    
    if (!metaData.isEmpty()) {
        info.name = metaData["name"].toString();
        info.version = metaData["version"].toString();
        info.description = metaData["description"].toString();
        
        QJsonArray deps = metaData["dependencies"].toArray();
        for (const QJsonValue &dep : deps) {
            info.dependencies << dep.toString();
        }
        
        QJsonArray perms = metaData["permissions"].toArray();
        for (const QJsonValue &perm : perms) {
            info.permissions << perm.toString();
        }
    }
    
    return info;
}

bool PluginSystem::checkDependencies(const PluginInfo &pluginInfo)
{
    // Check if all dependencies are available
    for (const QString &dependency : pluginInfo.dependencies) {
        bool found = false;
        for (const PluginInfo &availablePlugin : m_availablePlugins.values()) {
            if (availablePlugin.name == dependency && availablePlugin.loaded) {
                found = true;
                break;
            }
        }
        if (!found) {
            qWarning() << "Dependency not found:" << dependency << "for plugin:" << pluginInfo.name;
            return false;
        }
    }
    return true;
}

void PluginSystem::setupPluginSandbox(const QString &pluginName)
{
    // Implement sandboxing mechanisms
    // This could include process isolation, resource limits, etc.
    qDebug() << "Setting up sandbox for plugin:" << pluginName;
}

void PluginSystem::cleanupPlugin(const QString &pluginName)
{
    // Cleanup any resources associated with the plugin
    qDebug() << "Cleaning up plugin:" << pluginName;
}

void PluginSystem::savePluginConfigs()
{
    QSettings settings(getConfigPath(), QSettings::IniFormat);
    settings.beginGroup("Plugins");
    
    for (const PluginInfo &info : m_availablePlugins.values()) {
        settings.beginGroup(info.name);
        settings.setValue("enabled", info.enabled);
        settings.setValue("config", QJsonDocument(info.config).toJson());
        settings.endGroup();
    }
    
    settings.endGroup();
}

void PluginSystem::loadPluginConfigs()
{
    QSettings settings(getConfigPath(), QSettings::IniFormat);
    settings.beginGroup("Plugins");
    
    QStringList pluginNames = settings.childGroups();
    for (const QString &pluginName : pluginNames) {
        settings.beginGroup(pluginName);
        
        bool enabled = settings.value("enabled", false).toBool();
        QByteArray configData = settings.value("config").toByteArray();
        QJsonObject config = QJsonDocument::fromJson(configData).object();
        
        // This will be populated when plugins are discovered
        if (m_availablePlugins.contains(pluginName)) {
            m_availablePlugins[pluginName].enabled = enabled;
            m_availablePlugins[pluginName].config = config;
        }
        
        settings.endGroup();
    }
    
    settings.endGroup();
}

QString PluginSystem::getConfigPath() const
{
    return QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/plugins.ini";
}

void PluginSystem::setupIPC()
{
    // Setup inter-process communication mechanisms
    // This could include named pipes, shared memory, sockets, etc.
    qDebug() << "Setting up IPC for plugin system";
}

void PluginSystem::handleIPCMessage(const QJsonObject &message)
{
    QString type = message["type"].toString();
    
    if (type == "event") {
        QString event = message["event"].toString();
        QJsonObject data = message["data"].toObject();
        
        if (m_eventHandlers.contains(event)) {
            m_eventHandlers[event](data);
        }
    }
}

// C API Implementation
extern "C" {
    static PluginSystem* g_pluginSystem = nullptr;
    
    bool audit_plugin_initialize(const char* config_json)
    {
        if (!g_pluginSystem) {
            g_pluginSystem = new PluginSystem();
        }
        
        QJsonDocument doc = QJsonDocument::fromJson(QByteArray(config_json));
        QJsonObject config = doc.object();
        
        // Initialize the plugin system with the provided configuration
        return true;
    }
    
    void audit_plugin_shutdown()
    {
        if (g_pluginSystem) {
            delete g_pluginSystem;
            g_pluginSystem = nullptr;
        }
    }
    
    bool audit_log_event(const AuditEvent* event)
    {
        if (!g_pluginSystem || !event) {
            return false;
        }
        
        // Convert C struct to Qt objects and log the event
        QJsonObject metadata;
        if (event->metadata_json) {
            QJsonDocument doc = QJsonDocument::fromJson(QByteArray(event->metadata_json));
            metadata = doc.object();
        }
        
        // This would call the appropriate audit logging functionality
        return true;
    }
    
    char* audit_query_events(const char* filter_json)
    {
        if (!g_pluginSystem || !filter_json) {
            return nullptr;
        }
        
        QJsonDocument filterDoc = QJsonDocument::fromJson(QByteArray(filter_json));
        QJsonObject filter = filterDoc.object();
        
        // Query events and return as JSON string
        QJsonArray results; // This would be populated with actual results
        QJsonDocument resultDoc(results);
        QByteArray resultData = resultDoc.toJson();
        
        char* result = new char[resultData.size() + 1];
        strcpy(result, resultData.constData());
        return result;
    }
    
    bool audit_export_data(const CExportConfig* config)
    {
        if (!g_pluginSystem || !config) {
            return false;
        }
        
        // Convert C struct to Qt objects and perform export
        return true;
    }
    
    bool audit_authenticate_user(const char* username, const char* password)
    {
        if (!g_pluginSystem || !username || !password) {
            return false;
        }
        
        // Perform user authentication
        return true;
    }
    
    bool audit_has_permission(const char* user_id, const char* permission)
    {
        if (!g_pluginSystem || !user_id || !permission) {
            return false;
        }
        
        // Check user permissions
        return true;
    }
    
    void audit_free_string(char* str)
    {
        delete[] str;
    }
    
    AuditPluginInfo audit_get_plugin_info()
    {
        AuditPluginInfo info;
        info.name = "AuditTrailSystem";
        info.version = "1.0.0";
        info.description = "Comprehensive audit trail system with three-tier permissions";
        return info;
    }
}